package org.orienteer.core.component.meta; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.markup.IMarkupFragment; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.html.form.ILabelProvider; import org.apache.wicket.markup.html.form.LabeledWebMarkupContainer; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.ComponentPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.util.lang.Objects; import org.apache.wicket.util.visit.ClassVisitFilter; import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitor; import org.orienteer.core.component.IExportable; import org.orienteer.core.service.IMarkupProvider; import com.google.inject.Inject; /** * {@link Panel} that can substitute required component according to a provided criteria * * @param <T> the type of an entity * @param <C> the type of a criteria * @param <V> the type of a value */ public abstract class AbstractMetaPanel<T, C, V> extends AbstractEntityAndPropertyAwarePanel<T, C, V> implements ILabelProvider<String>, IExportable<Object> { private static final long serialVersionUID = 1L; private static final String PANEL_ID = "panel"; private Serializable stateSignature; private IModel<String> labelModel; @Inject private IMarkupProvider markupProvider; private Component component; public AbstractMetaPanel(String id, IModel<T> entityModel, IModel<C> propertyModel, IModel<V> valueModel) { super(id, entityModel, propertyModel, valueModel); } public AbstractMetaPanel(String id, IModel<T> entityModel, IModel<C> propertyModel) { super(id, entityModel, propertyModel); } @Override protected void onConfigure() { super.onConfigure(); C critery = getPropertyObject(); Serializable newSignature = getSignature(critery); if(!newSignature.equals(stateSignature) || get(PANEL_ID)==null) { stateSignature = newSignature; component = resolveComponent(PANEL_ID, critery); onPostResolveComponent(component, critery); // component.setOutputMarkupId(true); addOrReplace(component); } } protected void onPostResolveComponent(Component component, C critery) { if(component instanceof LabeledWebMarkupContainer) { ((LabeledWebMarkupContainer)component).setLabel(getLabel()); } } protected Serializable getSignature(C critery) { return Objects.hashCode(critery); } @Override public IMarkupFragment getMarkup(Component child) { if(child==null) return super.getMarkup(child); IMarkupFragment ret = markupProvider.provideMarkup(child); return ret!=null?ret:super.getMarkup(child); } @Override public IModel<String> getLabel() { if(labelModel==null) { labelModel = newLabelModel(); } return labelModel; } @SuppressWarnings("unchecked") public IMetaContext<C> getMetaContext() { return getMetaContext(this); } @SuppressWarnings("unchecked") public static <C> IMetaContext<C> getMetaContext(Component component) { return (IMetaContext<C>) component.visitParents(MarkupContainer.class, new IVisitor<MarkupContainer, IMetaContext<C>>() { @Override public void component(MarkupContainer object, IVisit<IMetaContext<C>> visit) { visit.stop((IMetaContext<C>)object); } }, new ClassVisitFilter(IMetaContext.class)); } public <W> AbstractMetaPanel<T, C, W> getMetaComponent(C critery) { return getMetaComponent(getMetaContext(), critery); } public <W> W getMetaComponentValue(C critery) { AbstractMetaPanel<T, C, W> otherMetaPanel = getMetaComponent(critery); return otherMetaPanel!=null?otherMetaPanel.getValueObject():null; } @SuppressWarnings("unchecked") public V getEnteredValue() { if(component instanceof FormComponent && ((FormComponent<V>)component).hasRawInput()) { FormComponent<V> formComponent = (FormComponent<V>)component; convertInput(formComponent); return formComponent.getConvertedInput(); } else { return getValueObject(); } } private void convertInput(FormComponent<V> formComponent) { try { Method convertInputMethod = FormComponent.class.getDeclaredMethod("convertInput"); convertInputMethod.setAccessible(true); convertInputMethod.invoke(formComponent); } catch (Exception e) { throw new WicketRuntimeException("Can't invoke 'convertInput' on component", e); } } public <W> W getMetaComponentEnteredValue(C critery) { AbstractMetaPanel<T, C, W> otherMetaPanel = getMetaComponent(critery); if(otherMetaPanel==null) return null; return otherMetaPanel!=null?otherMetaPanel.getEnteredValue():null; } public IModel<?> getMetaComponentEnteredValueModel(final C critery) { return new AbstractReadOnlyModel<Object>() { @Override public Object getObject() { return getMetaComponentEnteredValue(critery); } }; } @SuppressWarnings("unchecked") public static <K extends AbstractMetaPanel<?, ?, ?>> K getMetaComponent(IMetaContext<?> context, final Object critery) { if(context==null || critery==null) return null; else return (K)context.getContextComponent() .visitChildren(AbstractMetaPanel.class, new IVisitor<AbstractMetaPanel<?, ?, ?>, AbstractMetaPanel<?, ?, ?>>() { @Override public void component( AbstractMetaPanel<?, ?, ?> object, IVisit<AbstractMetaPanel<?, ?, ?>> visit) { if(Objects.isEqual(object.getPropertyObject(), critery)) visit.stop(object); else visit.dontGoDeeper(); } }); } public IModel<?> getExportableDataModel() { configure(); if(component instanceof IExportable){ return ((IExportable<?>) component).getExportableDataModel(); } else if(component instanceof Label) { return Model.of(component.getDefaultModelObjectAsString()); } else return getModel(); } protected abstract IModel<String> newLabelModel(); protected abstract Component resolveComponent(String id, C critery); }